/*
 * Copyright 2008-2009 Aying.Net Powered by Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.util.Date;
import java.util.List;
import java.util.Collection;
import java.math.BigDecimal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.aying.util.resources.Resources;

/**
 * 包含文件、文件流处理常用方法的工具类。
 * <p />
 * 类中的部份方法及代码来自于 {@code Apache Commons IO 1.3 released}
 * 
 * @author Fuchun
 * @version 1.0, 2009-06-22
 */
public class FileUtils extends IOUtils {

    @SuppressWarnings("unused")
    private static final Log log = LogFactory.getLog(FileUtils.class);

    /** 一千字节({@code 1KB})的字节数 - 1024。 */
    public static final long KILOBYTE = 1024;

    /** 一兆字节({@code 1MB})的字节数 - 1024 * 1024。 */
    public static final long MEGABYTE = KILOBYTE * KILOBYTE;

    /** 一千兆字节({@code 1GB})的字节数 - 1024 * 1024 * 1024。 */
    public static final long GIGABYTE = KILOBYTE * MEGABYTE;

    /** 一兆兆字节({@code 1TB})的字节数 - 1024 * 1024 * 1024 * 1024。 */
    public static final long TERABYTE = KILOBYTE * GIGABYTE;

    /** 字节的缩写单位 - {@code B}。 */
    public static final String UNIT_BYTE = "B";

    /** 千字节的缩写单位 - {@code KB}。 */
    public static final String UNIT_KILOBYTE = "KB";

    /** 兆字节的缩写单位 - {@code MB}。 */
    public static final String UNIT_MEGABYTE = "MB";

    /** 千兆字节的缩写单位 - {@code GB}。 */
    public static final String UNIT_GIGABYTE = "GB";

    /** 兆兆字节的缩写单位 - {@code TB}。 */
    public static final String UNIT_TERABYTE = "TB";

    /** 一个固定的 {@code File} 类型的空数组。 */
    public static final File[] EMPTY_FILE_ARRAY = new File[0];

    // 错误信息健常量
    // -------------------------------------------------------------------------
    private static final String MUST_NOT_BE_NULL = "FileUtils.FileMustNotBeNull";
    private static final String MUST_NOT_BE_EMPTY = "FileUtils.FileMustNotBeEmpty";
    private static final String MUST_BE_FILE = "FileUtils.FileMustBeFile";
    private static final String FILE_CANNOT_READ = "FileUtils.FileCannotRead";
    private static final String FILE_CANNOT_FOUND = "FileUtils.FileCannotFound";
    private static final String FILE_CANNOT_WRITE = "FileUtils.FileCannotWrite";
    private static final String FILE_CANNOT_BE_CREATED = "FileUtils.FileCannotBeCreated";
    private static final String MUST_BE_DIRECTORY = "FileUtils.FileMustBeDirectory";
    private static final String DIRECTORY_CANNOT_BE_CREATED = "FileUtils.DirectoryCannotBeCreated";
    private static final String DIRECTORY_ACCESS_DENIED = "FileUtils.DirectoryAccessDenied";
    private static final String FILE_CANNOT_BE_DELETED = "FileUtils.FileCannotBeDeleted";
    private static final String FILE_CANNOT_LASTMODIFY = "FileUtils.FileCannotLastModify";
    private static final String FILE_ARE_THE_SAME = "FileUtils.FileAreTheSame";
    private static final String FILE_ALREADY_EXISTS = "FileUtils.FileAlreadyExists";
    private static final String FILE_MOVE_FILURE = "FileUtils.FileMoveFailure";
    // -------------------------------------------------------------------------

    private static final String FILE_FILE = "file";
    private static final String DIRECTORY = "directory";
    private static final String SOURCE_FILE = "srcFile";
    private static final String DEST_FILE = "destFile";
    private static final String SOURCE_DIRECTORY = "srcDir";
    private static final String DEST_DIRECTORY = "destDir";

    

    /**
     * 返回给定 {@code file} 对象表示的文件扩展名。若 {@code file} 为 {@code null}、
     * 不存在或者表示一个文件目录，则返回 {@code null}。
     * <p />
     * <strong>注意：一个没有扩展名的常规文件返回空字符串。</strong>
     * 
     * @param file 要获得扩展名的文件。
     * @return 给定 {@code file} 对象表示的文件扩展名。
     */
    public static String getExtension(File file) {
        // 文件为NULL、不存在或者表示为一个文件目录，扩展名返回NULL
        if (file == null || !file.exists() || file.isDirectory()) {
            return null;
        }
        String fileName = file.getName();
        int pos = fileName.lastIndexOf(".");
        if (pos >= 0) {
            return fileName.substring(pos + 1);
        } else {
            return EMPTY;
        }
    }

    /**
     * 返回给定路径表示的文件的扩展名。若 {@code filePath} 为 {@code null}、空字符串、 或者表示一个文件目录，则返回
     * {@code null}。注意：一个没有扩展名的文件也返回空字符串。
     * 
     * @param filePath 给定的文件路径。
     * @return 给定文件路径的文件扩展名。
     */
    public static String getExtension(String filePath) {
        if (isEmpty(filePath)) {
            return null;
        }
        return getExtension(new File(filePath));
    }

    /**
     * 用 {@code FileInputStream} 包装器打开指定的文件 {@code file} 对象。此方法提供比直接使用
     * {@code new FileInputStream(file)} 方式构造 {@code FileInputStream} 更多的调试信息！
     * <ul>
     * <li>若 {@code file} 不存在，则抛出 {@code FileNotFoundException} 异常。</li>
     * <li>若 {@code file} 表示一个文件夹，则抛出 {@code IOException} 异常。</li>
     * <li>若 {@code file} 文件不可读，则抛出 {@code IOException} 异常。</li>
     * </ul>
     * 
     * @param file 要打开的 {@code File} 对象。
     * @return 指定 {@code file} 的 {@code FileInputStream} 对象。
     * @throws FileNotFoundException 若指定的{@code file} 对象表示的文件不存在。
     * @throws IOException
     *         <ul>
     *         <li>若指定的 {@code file} 对象表示一个文件夹。</li>
     *         <li>若指定的 {@code file} 对象表示的文件不可读。</li>
     *         </ul>
     */
    public static FileInputStream openInputStream(File file) throws IOException {
        if (file == null) {
            // "指定的 file 不能为 null。"
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, FILE_FILE));
        }
        if (file.exists()) {
            if (file.isDirectory()) {
                // "指定的 file --> \" + file + \" 不是一个文件！"
                throw new IOException(Resources.getText(MUST_BE_FILE, FILE_FILE, file));
            }
            if (!file.canRead()) {
                // "指定的 file --> \"" + file + "\" 不可读。"
                throw new IOException(Resources.getText(FILE_CANNOT_READ, FILE_FILE, file));
            }
        } else {
            // "指定的 file --> \"" + file + "\" 不存在。"
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, FILE_FILE + file));
        }
        return new FileInputStream(file);
    }

    /**
     * 创建一个向指定 {@code File} 对象表示的文件中写入数据的文件输出流。
     * <p />
     * 如果 {@code file} 的父目录不存在，则自动创建 {@code file} 的所有父目录。
     * 
     * @param file 为了进行写入而打开的文件。
     * @return 指定 {@code file} 的 {@code FileOutputStream} 对象。
     * @throws IOException
     *         <ul>
     *         <li>若 {@code file} 是一个目录，而不是一个常规文件！。</li>
     *         <li>若 {@code file} 不能被写入。</li>
     *         <li>若 {@code file} 不存在，但不能创建它。</li>
     *         </ul>
     * @throws FileNotFoundException 因为某些原因而无法打开 {@code file}。
     */
    public static FileOutputStream openOutputStream(File file) throws IOException {
        if (file == null) {
            // "指定的 file 不能为 null。"
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, FILE_FILE));
        }
        if (file.exists()) {
            if (file.isDirectory()) {
                // "指定的 file --> \" + file + \" 不是一个文件！"
                throw new IOException(Resources.getText(MUST_BE_FILE, FILE_FILE, file));
            }
            if (!file.canWrite()) {
                // "指定的 file --> \"" + file + "\" 不可写。"
                throw new IOException(Resources.getText(FILE_CANNOT_WRITE, FILE_FILE, file));
            }
        } else {
            File parent = file.getParentFile();
            if (parent != null && !(parent.exists())) {
                if (!parent.mkdirs()) {
                    // "指定的 file --> \"" + file + "\" 不能被创建。"
                    throw new IOException(Resources
                            .getText(FILE_CANNOT_BE_CREATED, FILE_FILE, file));
                }
            }
        }
        return new FileOutputStream(file);
    }

    /**
     * 创建指定路径名的目录，包括所有必需但不存在的父目录。
     * <p />
     * <strong>注意，此操作失败时也可能已经成功地创建了一部分必需的父目录。</strong>
     * 
     * @param directoryPath 要创建的所有必需但不存在的父目录的路径。
     * @throws NullPointerException 如果 {@code directoryPath} 为 {@code null}
     *         或空字符串。
     * @throws IOException 如果没有创建目录以及所有必需的父目录。
     */
    public static void mkdirs(String directoryPath) throws IOException {
        if (isEmpty(directoryPath)) {
            // "指定的 directoryPath 不能为 null 或空字符串。"
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_EMPTY, "directoryPath"));
        }
        mkdirs(new File(directoryPath));
    }

    /**
     * 创建一个文件目录，包括所有必需但不存在的父目录。
     * <p />
     * 如果 {@code directory} 为 {@code null}，则抛出 {@code NullPointerException}；
     * 如果 {@code directory} 已经存在，并且表示一个常规文件；或者无法创建指定文件目录，则抛出异常。
     * 
     * @param directory 要创建的文件目录。
     * @throws NullPointerException 如果 {@code directory} 为 {@code null}。
     * @throws IOException 如果 {@code directory} 已经存在，并且表示一个常规文件；或者无法创建指定的文件目录。
     */
    public static void mkdirs(File directory) throws IOException {
        if (directory == null) {
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DIRECTORY));
        }
        if (directory.exists()) {
            if (directory.isFile()) {
                // "指定的 directory --> \"" + directory + "\" 不是一个文件目录。"
                throw new IOException(Resources.getText(MUST_BE_DIRECTORY, DIRECTORY + directory));
            }
        } else {
            if (!directory.mkdirs()) {
                // "指定的 directory --> \"" + directory + "\" 不能被创建。"
                throw new IOException(Resources.getText(FILE_CANNOT_BE_CREATED, DIRECTORY
                        + directory));
            }
        }
    }

    /**
     * 创建一个给定父目录和目录名称的目录。该方法执行过程中将自动创建包括给定父目录中所有必需但不存在的父目录。
     * <p />
     * 若要创建的目录名称参数 {@code directoryName} 为 {@code null} 或空字符串，则使用当天日期组成的名称({@code yyyyMMdd})。
     * 
     * @param directory 给定的父目录。
     * @param directoryName 要创建的目录名称。
     * @throws NullPointerException 如果 {@code directory} 为 {@code null}。
     * @throws IOException 如果 {@code directory} 不是一个目录；或者无法创建 {@code directory}
     *         中包含的所有必需但不存在的父目录。
     */
    public static void mkdir(File directory, String directoryName) throws IOException {
        if (directory == null) {
            // "指定的 directory 不能为 null。"
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DIRECTORY));
        }
        if (isEmpty(directoryName)) {
            directoryName = DateUtils.format("yyyyMMdd");
        }
        if (directory.exists()) {
            if (!directory.isDirectory()) {
                // "指定的 directory --> \"" + directory + "\" 不是一个文件目录。"
                throw new IOException(Resources.getText(MUST_BE_DIRECTORY, DIRECTORY, directory));
            } else {
                mkdirs(directory.getAbsolutePath() + DIR_SEPARATOR + directoryName);
            }
        } else {
            if (directory.mkdirs()) {
                mkdirs(directory.getAbsolutePath() + DIR_SEPARATOR + directoryName);
            } else {
                // "不能创建 directory --> \"" + directory + "\"，包括必需的但不存在的父目录。"
                throw new IOException(Resources.getText(DIRECTORY_CANNOT_BE_CREATED, DIRECTORY,
                        directory));
            }
        }
    }

    // calculate files or directories count|size
    // -----------------------------------------------------------------------
    /**
     * 递归方式计算给定文件目录下的指定扩展名的文件的数目。
     * 
     * @param directory 给定的文件目录。
     * @param exts 指定的一组扩展名。
     * @return 给定文件目录下的文件数目。
     */
    public static int countOf(File directory, String... exts) {
        if (directory == null || !directory.exists())
            return 0;
        boolean directExt = isNotEmpty(exts);
        if (directory.isFile()) {
            if (directExt) {
                if (StringUtils.inArray(getExtension(directory), exts))
                    return 1;
                return 0;
            } else {
                return 0;
            }
        }
        int count = 0;
        if (directory.isDirectory()) {
            File[] files = directory.listFiles();
            for (File f : files) {
                if (f.isFile()) {
                    if (!directExt || StringUtils.inArray(getExtension(f), exts)) {
                        count += 1;
                    }
                } else {
                    count += countOf(f, exts);
                }
            }
        }
        return count;
    }

    /**
     * 计算并返回给定 {@code File} 表示的文件大小或者目录下的所有文件大小的总合。
     * <p />
     * <strong>注意：给定的 {@code File} 为 {@code null}、不存在或拒绝访问时返回0。</strong>
     * 
     * @param dest 要计算大小的 {@code File}。
     * @return 给定 {@code File} 表示的文件大小或者目录下的所有文件大小的总合。
     */
    public static long sizeOf(File dest) {
        if (dest == null || !dest.exists()) {
            return 0L;
        }
        if (dest.isFile())
            return dest.length();
        File[] files = dest.listFiles();
        if (files == null) { // 安全管理器拒绝对目录进行读访问
            return 0L;
        }
        long size = 0;
        for (int i = 0, len = files.length; i < len; i++) {
            File file = files[i];
            if (file.isFile()) {
                size += file.length();
            } else if (file.isDirectory()) {
                size += sizeOf(file);
            }
        }
        return size;
    }

    /**
     * 计算并格式化给定的表示容量大小的可读字符串。
     * <p />
     * 例如：{@code 1023 B、2.02 KB、28.20 MB、1.29 GB} 等。 <table
     * style="font-family:monospace;" border="1">
     * <tr>
     * <th width="200" align="left">size</th>
     * <th width="120" align="left">scale = 1</th>
     * <th width="120" align="left">scale = 2</th>
     * </tr>
     * <tr>
     * <td>131</td>
     * <td>131 B</td>
     * <td>131 B</td>
     * </tr>
     * <tr>
     * <td>13100</td>
     * <td>12.8 KB</td>
     * <td>12.79 KB</td>
     * </tr>
     * <tr>
     * <td>13100000</td>
     * <td>12.5 MB</td>
     * <td>12.49 MB</td>
     * </tr>
     * <tr>
     * <td>1310000000</td>
     * <td>1.2 GB</td>
     * <td>1.22 GB</td>
     * </tr>
     * <tr>
     * <td>1310000000000L</td>
     * <td>1.2 TB</td>
     * <td>1.19 TB</td>
     * </tr>
     * </table>
     * 
     * @param size 要计算的容量大小（单位：bytes）。
     * @param scale 结果的精度（即小数点后面的位数）。
     * @return 给定的表示容量大小的可读字符串。
     */
    public static String countToDisplaySize(long size, int... scale) {
        StringBuilder displaySize = new StringBuilder();
        BigDecimal result = null;
        String sUnit = null;
        // 结果的精度
        int iScale = (scale == null || scale.length == 0 || scale[0] < 0) ? 2 : scale[0];
        if (size > TERABYTE) {
            result = MathUtils.divide(size, TERABYTE, iScale);
            sUnit = UNIT_TERABYTE;
        } else if (size > GIGABYTE) {
            result = MathUtils.divide(size, GIGABYTE, iScale);
            sUnit = UNIT_GIGABYTE;
        } else if (size > MEGABYTE) {
            result = MathUtils.divide(size, MEGABYTE, iScale);
            sUnit = UNIT_MEGABYTE;
        } else if (size > KILOBYTE) {
            result = MathUtils.divide(size, KILOBYTE, iScale);
            sUnit = UNIT_KILOBYTE;
        } else {
            result = size < 0 ? BigDecimal.ZERO : new BigDecimal(size);
            result.setScale(iScale);
            sUnit = UNIT_BYTE;
        }
        displaySize.append(result).append(WHITESPACE).append(sUnit);
        return displaySize.toString();
    }

    // delete files or directorys
    // -----------------------------------------------------------------------
    /**
     * 删除 {@code directory} 目录下的所有子文件和子目录，但是 {@code directory} 目录不会被删除。
     * 
     * @param directory 要清空的目录。
     * @throws IOException 如果 {@code directory} 目录下的文件或目录删除失败。
     */
    public static void cleanDirectory(File directory) throws IOException {
        if (directory == null || !directory.exists() || !directory.isDirectory())
            return;
        File[] files = directory.listFiles();
        if (files == null) { // 安全管理器拒绝对目录进行读访问
            // "不能访问 directory --> \"" + directory + "\" 目录下的文件。"
            throw new IOException(Resources.getText(DIRECTORY_ACCESS_DENIED, DIRECTORY, directory));
        }
        IOException exception = null;
        for (int i = 0, len = files.length; i < len; i++) {
            File file = files[i];
            try {
                delete(file);
            } catch (IOException ex) {
                exception = ex;
            }
        }

        if (exception != null)
            throw exception;
    }

    /**
     * 删除给定的 {@code file}。如果 {@code file} 表示一个文件目录，则删除它及它包含的所有子目录和文件。
     * <p />
     * <strong>注意：此方法一旦执行将无法取消，它将删除指定的文件或目录下的所有文件，请慎用。</strong>
     * 
     * @param file 要删除的 {@code File}。
     * @throws IOException 如果 {@code file} 删除失败。
     */
    public static void delete(File file) throws IOException {
        if (file == null)
            return;
        if (file.isDirectory()) {
            deleteDirectory(file);
        } else {
            boolean filePresent = file.exists();
            if (!file.delete()) {
                if (!filePresent) {
                    return;
                }
                // "指定的 file --> \"" + file + "\" 删除失败。"
                throw new IOException(Resources.getText(FILE_CANNOT_BE_DELETED, FILE_FILE, file));
            }
        }
    }

    /**
     * 递归删除给定的文件目录及目录下的所有文件。
     * 
     * @param directory 要删除的文件目录。
     * @throws IOException 如果目录删除失败。
     */
    public static void deleteDirectory(File directory) throws IOException {
        if (directory == null || !directory.exists())
            return;
        cleanDirectory(directory);
        if (!directory.delete()) {
            // "指定的 file --> \"" + file + "\" 删除失败。"
            throw new IOException(Resources.getText(FILE_CANNOT_BE_DELETED, DIRECTORY, directory));
        }
    }

    // copy files or directory
    // -----------------------------------------------------------------------
    /**
     * 内部使用的拷贝文件的方法。
     * <p />
     * 如果 {@code destFile} 存在，并且是一个文件，它将会被 {@code srcFile} 覆盖。 如果
     * {@code destFile} 存在，并且是一个文件目录，{@code srcFile} 源文件将被拷贝到 {@code destFile}
     * 目录下。
     * 
     * @param srcFile 有效的源文件，不能为 {@code null}。
     * @param destFile 有效的目的地文件，不能为 {@code null}。
     * @param preserveFileDate 是否保持文件的修改时间戳。
     * @throws IOException 在拷贝过程中发生 I/O 错误时，抛出此异常。
     */
    private static void doCopyFile(File srcFile, File destFile, boolean... preserveFileDate)
            throws IOException {
        FileInputStream input = new FileInputStream(srcFile);
        try {
            FileOutputStream output = new FileOutputStream(destFile);
            try {
                copy(input, output);
            } finally {
                close(output, true);
            }
        } finally {
            close(input, true);
        }

        if (srcFile.length() != destFile.length()) {
            // "Failed to copy full contents from '" + srcFile + "' to '" +
            // destFile + "'"
            throw new IOException("文件 \"" + srcFile + "\" 到 \"" + destFile + "\" 的拷贝失败！");
        }

        boolean preserve = preserveFileDate == null || preserveFileDate.length == 0 ? true
                : preserveFileDate[0];
        if (preserve) {
            destFile.setLastModified(srcFile.lastModified());
        }
    }

    /**
     * 拷贝一个文件到一个指定的文件目录下。
     * <p />
     * 该方法拷贝一份与源 {@code srcFile} 同样内容的文件到指定的目标文件目录。如果目标文件目录不存在，则自动创建。
     * 如果目标文件目录下存在一个与源 {@code srcFile} 同名的文件，则覆盖它。
     * 
     * @param srcFile 要拷贝的源文件，不能为 {@code null}。
     * @param destDir 拷贝文件的目标文件目录，不能为 {@code null}。
     * @param preservFileDate 文件拷贝成功后是否不改变文件时间戳。
     * @throws NullPointerException 如果源文件 {@code srcFile} 或目标文件目录
     *         {@code destDir} 为 {@code null}。
     * @throws IllegalArgumentException 如果源文件 {@code srcFile} 或目标文件目录
     *         {@code destDir} 无效。
     * @throws IOException 如果拷贝过程中发生 {@code I/O} 错误。
     * @see #copyFile(File, File, boolean)
     */
    public static void copyFileToDirectory(File srcFile, File destDir, boolean... preservFileDate)
            throws IOException {
        if (destDir == null) {
            // "Destination must not be null"
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (destDir.exists() && !destDir.isDirectory()) {
            // "Destination \"" + destDir + "\" is not a directory"
            throw new IllegalArgumentException(Resources.getText(MUST_BE_DIRECTORY, DEST_DIRECTORY,
                    destDir));
        }
        copyFileToDirectory(srcFile, new File(destDir, srcFile.getName()), preservFileDate);
    }

    /**
     * 拷贝一个文件到一个指定的目的地。
     * <p />
     * 该方法拷贝一份与源 {@code srcFile} 同样内容的文件到指定的目标文件。目标文件的必需的但不存在的父目录将被创建。
     * 如果目标文件已存在，则覆盖它。
     * 
     * @param srcFile 要拷贝的源文件。
     * @param destFile 被覆盖或创建的目标文件。
     * @param preserveFileDate 拷贝后是否保持源文件的最后时间戳。
     * @throws NullPointerException 如果源文件 {@code srcFile} 或目标文件 {@code destFile}
     *         为 {@code null}。
     * @throws FileNotFoundException 如果源文件 {@code srcFile} 不存在。
     * @throws IOException 如果源文件 {@code srcFile} 或目标文件 {@code destFile} 无效。
     * @throws IOException 如果拷贝过程中发生 I/O 错误。
     * @see #copyFileToDirectory(File, File, boolean)
     */
    public static void copyFile(File srcFile, File destFile, boolean... preserveFileDate)
            throws IOException {
        if (srcFile == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_FILE));
        }
        if (destFile == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_FILE));
        }
        if (!srcFile.exists()) {
            // "Source \"" + srcFile + "\" does not exist"
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, SOURCE_FILE,
                    srcFile));
        }
        if (srcFile.isDirectory()) {
            // "Source \"" + srcFile + "\" exists but is a directory"
            throw new IOException(Resources.getText(MUST_BE_FILE, SOURCE_FILE, srcFile));
        }
        if (srcFile.getCanonicalFile().equals(destFile.getCanonicalFile())) {
            // "Source \"" + srcFile + "\" and destination \"" + destFile + "\"
            // are the same"
            throw new IOException(Resources.getText(FILE_ARE_THE_SAME, SOURCE_FILE, srcFile,
                    DEST_FILE, destFile));
        }
        if (destFile.getParentFile() != null && !destFile.getParentFile().exists()) {
            if (!destFile.getParentFile().mkdirs()) {
                // "Destination \"" + destFile + "\" directory cannot be
                // created"
                throw new IOException(Resources.getText(DIRECTORY_CANNOT_BE_CREATED, DEST_FILE,
                        destFile));
            }
        }
        if (destFile.exists() && !destFile.canWrite()) {
            // "Destination \"" + destFile + "\" exists but is read-only"
            throw new IOException(Resources.getText(FILE_CANNOT_WRITE, DEST_FILE, destFile));
        }
        doCopyFile(srcFile, destFile, preserveFileDate);
    }

    /**
     * 拷贝一个目录到另一个目录下，并且不更改文件的时间戳。
     * <p />
     * 该方法拷贝指定的源文件目录及目录里的所有内容到指定的目标文件目录下。
     * <p />
     * 如果目标文件目录下源文件目录已存在，则优先使用 {@code srcDir} 和 {@code destDir}
     * 下包含的目录合并并覆盖相同的文件。
     * 
     * @param srcDir 源文件目录。
     * @param destDir 目标文件目录。
     * @throws NullPointerException 如果源目录 {@code srcDir} 或目标目录 {@code destDir} 为
     *         {@code null}。
     * @throws IllegalArgumentException 如果源 {@code srcDir} 或目标 {@code destDir}
     *         不表示一个文件目录。
     * @throws IOException 拷贝过程中发生 {@code I/O} 错误。
     */
    public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
        if (srcDir == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_DIRECTORY));
        }
        if (srcDir.exists() && !srcDir.isDirectory()) {
            // "Source \"" + srcDir + "\" is not a directory"
            throw new IllegalArgumentException(Resources.getText(MUST_BE_DIRECTORY,
                    SOURCE_DIRECTORY, srcDir));
        }
        if (destDir == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (destDir.exists() && !destDir.isDirectory()) {
            // "Destination \"" + destDir + "\" is not a directory"
            throw new IllegalArgumentException(Resources.getText(MUST_BE_DIRECTORY, DEST_DIRECTORY,
                    destDir));
        }
        copyDirectory(srcDir, new File(destDir, srcDir.getName()), true);
    }

    /**
     * 拷贝一个文件目录到一个指定的目的地。
     * <p />
     * 该方法拷贝一个指定的源文件目录到一个指定的目标文件目录中。目标文件目录必需的但不存在的父目录将被创建。
     * <p />
     * 如果目标文件目录已存在，则优先使用 {@code srcDir} 和 {@code destDir} 包含的目录合并并覆盖相同的文件。
     * 
     * @param srcDir 源文件目录。
     * @param destDir 目标文件目录。
     * @param preserveFileDate 拷贝后是否保持源文件的最后时间戳。
     * @throws NullPointerException 如果源目录 {@code srcDir} 或目标目录 {@code destDir} 为
     *         {@code null}。
     * @throws FileNotFoundException 如果源目录 {@code srcDir} 不存在。
     * @throws IOException 如果源目录 {@code srcDir} 或目标目录 {@code destDir} 无效。
     * @throws IOException 如果拷贝过程中发生 {@code I/O} 错误。
     */
    public static void copyDirectory(File srcDir, File destDir, boolean... preserveFileDate)
            throws IOException {
        copyDirectory(srcDir, destDir, null, preserveFileDate);
    }

    /**
     * 拷贝一个包含文件过滤器的文件目录到一个指定的目的地。
     * <p />
     * 该方法拷贝一个指定的源文件目录到一个指定的目标文件目录中。目标文件目录必需的但不存在的父目录将被创建。
     * <p />
     * 如果目标文件目录已存在，则优先使用 {@code srcDir} 和 {@code destDir} 包含的目录合并并覆盖相同的文件。
     * 
     * @param srcDir 源文件目录。
     * @param destDir 目标文件目录。
     * @param filter 文件过滤器，可能为 {@code null}。
     * @param preserveFileDate 拷贝后是否保持源文件的最后时间戳。
     * @throws NullPointerException 如果源目录 {@code srcDir} 或目标目录 {@code destDir} 为
     *         {@code null}。
     * @throws FileNotFoundException 如果源目录 {@code srcDir} 不存在。
     * @throws IOException 如果源目录 {@code srcDir} 或目标目录 {@code destDir} 无效。
     * @throws IOException 如果拷贝过程中发生 {@code I/O} 错误。
     */
    public static void copyDirectory(File srcDir, File destDir, FileFilter filter,
            boolean... preserveFileDate) throws IOException {
        if (srcDir == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_DIRECTORY));
        }
        if (destDir == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (!srcDir.exists()) {
            // "Source \"" + srcDir + "\" does not exist"
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, SOURCE_DIRECTORY,
                    srcDir));
        }
        if (!srcDir.isDirectory()) {
            // "Source \"" + srcDir + "\" exists but is not a directory"
            throw new IOException(Resources.getText(MUST_BE_DIRECTORY, SOURCE_DIRECTORY, srcDir));
        }
        if (srcDir.getCanonicalFile().equals(destDir.getCanonicalFile())) {
            // "Source \"" + srcDir + "\" and destination \"" + destDir + "\"
            // are the same"
            throw new IOException(Resources.getText(FILE_ARE_THE_SAME, SOURCE_DIRECTORY, srcDir,
                    DEST_DIRECTORY, destDir));
        }
        List<String> exclusionList = null;
        if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
            File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
            if (srcFiles != null && srcFiles.length > 0) {
                exclusionList = GenericUtils.getList(srcFiles.length);
                for (int i = 0, len = srcFiles.length; i < len; i++) {
                    File copiedFile = new File(destDir, srcFiles[i].getName());
                    exclusionList.add(copiedFile.getCanonicalPath());
                }
            }
        }
        doCopyDirectory(srcDir, destDir, filter, exclusionList, preserveFileDate);
    }

    /**
     * 该类内部使用的拷贝文件目录的方法。
     * 
     * @param srcDir 源文件目录。
     * @param destDir 目标文件目录。
     * @param filter 文件过滤器，若为 {@code null} 表示拷贝所有目录和文件。
     * @param exclusionList 拷贝时排除的路径的列表，可能为 {@code null}。
     * @param preserveFileDate 是否保留文件的最后修改时间戳。
     * @throws IOException 如果拷贝发生错误。
     */
    private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter,
            List<String> exclusionList, boolean... preserveFileDate) throws IOException {
        if (destDir.exists()) {
            if (!destDir.isDirectory()) {
                // "Destination \"" + destDir + "\" exists but is not a
                // directory"
                throw new IOException(Resources.getText(MUST_BE_DIRECTORY, DEST_DIRECTORY, destDir));
            }
        } else {
            if (!destDir.mkdirs()) {
                // "Destination \"" + destDir + "\" directory cannot be created"
                throw new IOException(Resources.getText(DIRECTORY_CANNOT_BE_CREATED,
                        DEST_DIRECTORY, destDir));
            }
            if (preserveFileDate != null && preserveFileDate.length > 0 && preserveFileDate[0]) {
                destDir.setLastModified(srcDir.lastModified());
            }
        }
        if (!destDir.canWrite()) {
            // "Destination \"" + destDir + "\" cannot be written to"
            throw new IOException(Resources.getText(FILE_CANNOT_WRITE, DEST_DIRECTORY, destDir));
        }
        File[] files = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
        if (files == null) {
            throw new IOException(Resources.getText(DIRECTORY_ACCESS_DENIED, DEST_DIRECTORY,
                    destDir));
        }
        for (int i = 0, len = files.length; i < len; i++) {
            File copiedFile = new File(destDir, files[i].getName());
            if (exclusionList == null || !exclusionList.contains(files[i].getCanonicalPath())) {
                if (files[i].isDirectory()) {
                    doCopyDirectory(files[i], copiedFile, filter, exclusionList, preserveFileDate);
                } else {
                    doCopyFile(files[i], copiedFile, preserveFileDate);
                }
            }
        }
    }

    /**
     * 实现一个与 {@code Unix} 系统 {@code touch} 命令同样功能的方法。该方法修改给定文件的存取(
     * {@code access} )和/或修改( {@code modification} )时间戳。
     * <p />
     * 如果给定的文件 {@code file} 已存在，则修改文件的时间戳为当前时间；
     * 若给定的文件不存在，则创建一个新的空文件（若父目录不存在，也会同时创建），并更新时间戳。
     * 
     * @param file 要更新时间戳的文件对象。
     * @throws NullPointerException 若给定的 {@code file} 为 {@code null} 时抛出。
     * @throws IOException
     *         <ul>
     *         <li>若给定的 {@code file} 对象表示一个目录时抛出。</li>
     *         <li>若给定的 {@code file} 对象为只读模式时抛出。</li>
     *         <li>若给定的 {@code file} 对象的父目录不能被创建时抛出。</li>
     *         <li>若给定的 {@code file} 对象的时间戳不能被修改时抛出。</li>
     *         </ul>
     */
    public static void touch(File file) throws IOException {
        if (!file.exists()) {
            OutputStream output = openOutputStream(file);
            close(output);
        }
        boolean success = file.setLastModified(System.currentTimeMillis());
        if (!success) {
            // "不能修改 file --> \"" + file + "\" 的存取(access)/修改(modification)时间戳。"
            throw new IOException(Resources.getText(FILE_CANNOT_LASTMODIFY, FILE_FILE, file));
        }
    }

    /**
     * 读取给定文件的内容并以字符串形式返回。该方法已经关闭文件流。
     * <p />
     * 如果指定的编码 {@code encoding} 为 {@code null} 或空字符串，则默认为当前 {@code JVM} 使用的编码。
     * 
     * @param file 要读取的文件
     * @param encoding 读取文件的编码。
     * @return 给定文件的内容。
     * @throws IOException 若读取过程中发生 {@code I/O} 错误。
     */
    public static String readFileToString(File file, String... encoding) throws IOException {
        InputStream input = null;
        try {
            input = openInputStream(file);
            if (hasValue(encoding))
                return toString(input, encoding[0]);
            else
                return toString(input);
        } finally {
            close(input);
        }
    }

    /**
     * 读取给定文件的内容以字节数组形式返回。该方法已经关闭文件流。
     * 
     * @param file 要读取的文件。
     * @return 给定文件内容的字节数组。
     * @throws IOException 若读取过程中发生 {@code I/O} 错误。
     */
    public static byte[] readFileToBytes(File file) throws IOException {
        InputStream input = null;
        try {
            input = openInputStream(file);
            return toByteArray(input);
        } finally {
            close(input);
        }
    }

    /**
     * 用指定的编码逐行读取指定的文件，返回以行为单位的列表。
     * <p />
     * 如果指定的编码 {@code encoding} 为 {@code null} 或空字符串，则默认为当前 {@code JVM} 使用的编码。
     * 
     * @param file 要读取的文件。
     * @param encodings 指定的编码。
     * @return 列表形式的文件的内容(一项相当于文件中的一行)。
     * @throws IOException 如果读取文件过程中发生 {@code I/O} 错误。
     */
    public static List<String> readLines(File file, String... encodings) throws IOException {
        InputStream input = null;
        try {
            input = openInputStream(file);
            String encoding = hasValue(encodings) ? encodings[0] : null;
            return readLines(file, encoding);
        } finally {
            close(input);
        }
    }

    /**
     * 将给定的字符串以指定的编码写入文件中。
     * <p />
     * 如果指定的编码 {@code encoding} 为 {@code null} 或空字符串，则默认为当前 {@code JVM} 使用的编码。
     * 
     * @param file 要写入数据的文件。
     * @param data 要写入的字符串数据。
     * @param encoding 编码。
     * @throws IOException 如果写入过程中发生 {@code I/O} 错误。
     */
    public static void writeStringToFile(File file, String data, String... encoding)
            throws IOException {
        OutputStream output = null;
        try {
            output = openOutputStream(file);
            if (hasValue(encoding))
                write(data.toCharArray(), output, encoding[0]);
        } finally {
            close(output);
        }
    }

    /**
     * 将给定的字节数组写入指定的文件中。
     * 
     * @param file 要写入数据的文件。
     * @param bytes 要写入的字节数组数据。
     * @throws IOException 如果写入过程中发生 {@code I/O} 错误。
     */
    public static void writeBytesToFile(File file, byte[] bytes) throws IOException {
        OutputStream output = null;
        try {
            output = openOutputStream(file);
            write(bytes, output);
        } finally {
            close(output);
        }
    }

    /**
     * 将给定的列表字符串以逐行的方式写入指定的文件中。
     * <p />
     * 如果指定的编码 {@code encoding} 为 {@code null} 或空字符串，则默认为当前 {@code JVM} 使用的编码。
     * 
     * @param file 要写入数据的文件。
     * @param lines 要写入的列表数据。
     * @param encoding 指定的编码。
     * @throws IOException 如果写入过程中发生 {@code I/O} 错误。
     */
    public static void writeLines(File file, Collection<String> lines, String... encoding)
            throws IOException {
        writeLines(file, lines, null, encoding);
    }

    /**
     * 将给定的列表字符串以逐行的方式写入指定的文件中，并且指定了换行符。
     * <p />
     * 如果指定的换行符 {@code newLine} 为 {@code null} 或空字符串，则默认为当前系统使用的换行符。
     * <p />
     * 如果指定的编码 {@code encoding} 为 {@code null} 或空字符串，则默认为当前 {@code JVM} 使用的编码。
     * 
     * @param file 要写入数据的文件。
     * @param lines 要写入的列表数据。
     * @param newLine 要写入的换行符。
     * @param encoding 指定的编码。
     * @throws IOException 如果写入过程中发生 {@code I/O} 错误。
     */
    public static void writeLines(File file, Collection<String> lines, String newLine,
            String... encoding) throws IOException {
        OutputStream output = null;
        try {
            output = openOutputStream(file);
            if (hasValue(encoding))
                writeLines(lines, output, newLine, encoding[0]);
            else
                writeLines(lines, output, newLine);
        } finally {
            close(output);
        }
    }

    /**
     * 测试指定文件 {@code file} 的最后修改时间是否比文件 {@code reference} 的最后修改时间晚。
     * 
     * @param file 要检测的文件。
     * @param reference 与 {@code file} 比较的文件。
     * @return 如果 {@code file} 的最后修改时间晚于 {@code reference} 文件的最后修改时间，则返回
     *         {@code true}。
     */
    public static boolean isFileNewer(File file, File reference) {
        if (reference == null) {
            throw new IllegalArgumentException(Resources.getText(MUST_NOT_BE_NULL, "reference"));
        }
        if (!reference.exists()) {
            throw new IllegalArgumentException(Resources.getText(FILE_CANNOT_FOUND, "reference"));
        }
        return isFileNewer(file, reference.lastModified());
    }

    /**
     * 测试指定文件的最后修改时间是否比指定的日期/时间更晚(即文件的最后修改时间是否大于指定的日期/时间)。
     * <p />
     * 此方法相当于
     * {@linkplain #isFileNewer(File, long) FileUtils.isFileNewer(file, date.getTime())}。
     * 
     * @param file 要检测的文件，不能为 {@code null}。
     * @param date 要比较的日期/时间，若为 {@code null}，则默认为当前日期/时间。
     * @return 如果文件 {@code file} 存在并且最后修改时间晚于指定的日期/时间，则返回 {@code true}。
     */
    public static boolean isFileNewer(File file, Date date) {
        if (date == null) {
            return isFileNewer(file, System.currentTimeMillis());
        }
        return isFileNewer(file, date.getTime());
    }

    /**
     * 测试指定文件的最后修改时间是否比指定的时间更晚(即文件的最后修改时间是否大于指定的时间)。
     * 
     * @param file 要检测的文件。
     * @param timeMillis 自 1970 年 1 月 1 日 00:00:00 GMT 计量的时间毫秒数。
     * @return 如果文件 {@code file} 存在并且最后修改时间晚于指定的时间毫秒数，则返回 {@code true}。
     */
    public static boolean isFileNewer(File file, long timeMillis) {
        if (file == null) {
            throw new IllegalArgumentException(Resources.getText(MUST_NOT_BE_NULL, FILE_FILE));
        }
        if (!file.exists()) {
            return false;
        }
        return file.lastModified() > timeMillis;
    }

    /**
     * 移动文件目录。
     * <p />
     * 如果源文件目录和目标文件目录处在同一个父目录下，则执行重命名动作。
     * <p />
     * 如果源文件目录和目标文件目录不在同一个父目录下，则先执行拷贝，再删除源目录。
     * 
     * @param srcDir 源文件目录
     * @param destDir 目标文件目录。
     * @throws NullPointerException 如果指定的源文件目录 {@code srcDir} 或目标文件目录
     *         {@code destDir} 为 {@code null}。
     * @throws FileNotFoundException 如果要移动的源文件目录 {@code srcDir} 不存在。
     * @throws IOException 如果移动过程中发生 {@code I/O} 错误。
     */
    public static void moveDirectory(File srcDir, File destDir) throws IOException {
        if (srcDir == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_DIRECTORY));
        }
        if (destDir == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (!srcDir.exists()) {
            // "Source \"" + srcDir + "\" does not exist"
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, SOURCE_DIRECTORY));
        }
        if (!srcDir.isDirectory()) {
            // "Source \"" + srcDir + "\" exists but is not a directory"
            throw new IOException(Resources.getText(MUST_BE_DIRECTORY, SOURCE_DIRECTORY, srcDir));
        }
        boolean rename = srcDir.renameTo(destDir);
        if (!rename) {
            copyDirectory(srcDir, destDir, true);
            deleteDirectory(srcDir);
            if (srcDir.exists()) {
                throw new IOException(Resources.getText(FILE_MOVE_FILURE, SOURCE_DIRECTORY, srcDir, DEST_DIRECTORY, destDir));
            }
        }
    }

    /**
     * 将指定的文件目录移动到另一个文件目录下。
     * <p />
     * 该方法移动一个文件目录到另一个文件目录下，执行成功的结果是：{@code srcDir} 成为 {@code destDir} 目录下的子目录。
     * 
     * @param srcDir 要移动的文件目录，不能为 {@code null}。
     * @param destDir 目标文件目录，不能为 {@code null}。
     * @param createDestDir 如果目标文件目录不存在，指示是否创建。
     * @throws NullPointerException 如果要移动的文件目录 {@code srcDir} 或者目标文件目录
     *         {@code destDir} 为 {@code null}。
     * @throws FileNotFoundException 如果要移动的文件目录 {@code srcDir} 不存在 或者
     *         如果目标文件目录不存在，并且 {@code createDestDir} 参数为 {@code false}。
     * @throws IOException 如果移动过程中发生 {@code I/O} 错误。
     */
    public static void moveDirectoryToDirectory(File srcDir, File destDir, boolean createDestDir)
            throws IOException {
        if (srcDir == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_DIRECTORY));
        }
        if (destDir == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (!destDir.exists() && createDestDir) {
            destDir.mkdirs();
        }
        if (!destDir.exists()) {
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, DEST_DIRECTORY,
                    destDir)
                    + " [createDestDir = " + createDestDir + "]");
        }
        if (!destDir.isDirectory()) {
            throw new IOException(Resources.getText(MUST_BE_DIRECTORY, DEST_DIRECTORY, destDir));
        }
        moveDirectory(srcDir, new File(destDir, srcDir.getName()));
    }

    /**
     * 移动文件。
     * <p />
     * 如果文件 {@code srcFile} 和 {@code destFile} 在同一父目录下，则只作简单的重命名操作。
     * 
     * @param srcFile 要移动的文件。
     * @param destFile 目的地。
     * @param overwrite 设置为 {@code true} 时，如果 {@code destFile} 存在，将被
     *        {@code srcFile} 覆盖；否则抛出 {@code IOException}。
     * @throws IOException 如果移动中发生 {@code I/O} 错误。
     */
    public static void moveFile(File srcFile, File destFile, boolean... overwrite)
            throws IOException {
        if (srcFile == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_FILE));
        }
        if (destFile == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_FILE));
        }
        if (!srcFile.exists()) {
            // "Source \"" + srcFile + "\" does not exist"
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, SOURCE_FILE));
        }
        if (destFile.exists()) {
            // 是否覆盖已存在的文件
            boolean isOverwrite = hasValue(overwrite) ? overwrite[0] : false;
            if (!isOverwrite) {
                // "Destination \"" + destFile + "\" already exists"
                throw new IOException(Resources.getText(FILE_ALREADY_EXISTS, DEST_FILE, destFile));
            }
        }
        if (destFile.isDirectory()) {
            throw new IOException(Resources.getText(MUST_BE_FILE, DEST_FILE));
        }
        boolean rename = srcFile.renameTo(destFile);
        if (!rename) {
            copyFile(srcFile, destFile, true);
            // 拷贝成功，但是源文件没有删除，删除已拷贝的文件，做事务回滚操作，并抛出I/O异常。
            if (!srcFile.delete()) {
                delete(destFile);
                throw new IOException("源文件 " + srcFile + " 拷贝到 " + destFile + " 后，未能删除源文件。");
            }
        }
    }

    /**
     * 移动文件到指定的目录下。
     * 
     * @param srcFile 要移动的源文件。
     * @param destDir 指定的文件目录。
     * @param createDestDir 是否创建 {@code destDir} 中必需但不存在的目录。
     * @param overwrite 如果指定的目录 {@code destDir} 中存在与 {@code srcFile}
     *        同名的文件，是否执行覆盖操作。
     * @throws NullPointerException 如果指定的 {@code srcFile} 或者 {@code destDir} 为
     *         {@code null}。
     * @throws FileNotFoundException 如果指定的目录 {@code destDir} 有必需但不存在的目录，但
     *         {@code createDestDir == false} 时。
     * @throws IOException 如果移动过程中发生 {@code I/O} 错误。
     */
    public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir,
            boolean... overwrite) throws IOException {
        if (srcFile == null) {
            // Source must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, SOURCE_FILE));
        }
        if (destDir == null) {
            // Destination must not be null
            throw new NullPointerException(Resources.getText(MUST_NOT_BE_NULL, DEST_DIRECTORY));
        }
        if (!destDir.exists() && createDestDir) {
            destDir.mkdirs();
        }
        if (!destDir.exists()) {
            throw new FileNotFoundException(Resources.getText(FILE_CANNOT_FOUND, DEST_DIRECTORY,
                    destDir)
                    + " [createDestDir = " + createDestDir + "] ");
        }
        if (!destDir.isDirectory()) {
            throw new IOException(Resources.getText(MUST_BE_DIRECTORY, DEST_DIRECTORY, destDir));
        }
        moveFile(srcFile, new File(destDir, srcFile.getName()), overwrite);
    }
    
    
    public static void main(String[] args) throws Exception {
//        String[] wmaPath = {
//                "G:\\Kugoo\\音乐\\张萌萌 - 二十年.wma", "G:\\Kugoo\\音乐\\弦子 - 芳草碧连天.wma",
//                "G:\\Kugoo\\音乐\\无级生 - Watch Me 2007.wma", "G:\\我的音乐\\Back Street Boys\\Backstreet boys -How did I fall in love with you.wma",
//                "G:\\我的音乐\\伴奏音乐\\胡歌 - 六月的雨.wma", "G:\\我的音乐\\伴奏音乐\\许巍 - 那一年.wma",
//                "G:\\我的音乐\\爱乐团\\爱乐团 - 因为有你.wma", "G:\\我的音乐\\华语杂集\\男歌手\\胡畔 - 奔跑.wma"
//        };
//        String mp3PathPrefix = "G:\\我的音乐\\许巍\\";
//        String[] mp3Path = {
//                "许巍 - 彩虹.mp3", "许巍 - 纯粹.mp3", "许巍 - 纯真.mp3", "许巍 - 丁香.mp3",
//                "许巍 - 方向.mp3", "许巍 - 浮躁.mp3", "许巍 - 故乡.mp3", "许巍 - 幻觉.mp3"
//        };
//        java.io.RandomAccessFile raf = null;
//        byte[] bytes = null;
//        for(String mp3 : mp3Path) {
//            raf = new java.io.RandomAccessFile(new File(mp3PathPrefix + mp3), "r");
//            raf.seek(raf.length() - 128);
//            bytes = new byte[128];
//            raf.read(bytes, 0, 128);
//            StringBuffer buffer = new StringBuffer();
//            for(int i = 0; i < bytes.length; i++) {
//                buffer.append((int)bytes[i] + " ");
//            }
//            System.out.println(buffer.toString());
//            String tag = new String(bytes, 0, 128, "Cp437");
//            int start = "TAG".length();
//            String title = tag.substring(start, start += 30).trim();
//            String singer = tag.substring(start, start += 30).trim();
//            title = new String(title.getBytes("Cp437"), "GB18030");
//            singer = new String(singer.getBytes("Cp437"), "GB18030");
//            System.out.println("歌曲： " + mp3 + " | 标题： " + title + " | 艺术家： " + singer);
//        }
    }
}
